package com.gopay.checkorder.service.task.self;

import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang3.time.StopWatch;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.gopay.checkorder.dao.BisBankOrderQueryDAO;
import com.gopay.checkorder.manager.BankOrderManager;
import com.gopay.checkorder.service.BcdlOrderSendBankService;
import com.gopay.checkorder.service.sentinel.BcdlOrderSentinel;
import com.gopay.checkorder.service.task.thread.IcbcBcdlOrderSendMTTaskThread;
import com.gopay.common.constants.autosettle.SettlementExchangeConstants;
import com.gopay.common.constants.bank.BankOrderStatus;
import com.gopay.common.constants.bank.BankOrderType;
import com.gopay.common.constants.bank.BisBankBusinessProd;
import com.gopay.common.constants.bank.BisBankInterfaceChannel;
import com.gopay.common.constants.proccode.ProcCodeConstants;
import com.gopay.common.domain.bank.BankOrder;
import com.gopay.common.domain.bank.BcdlResult;
import com.gopay.common.exception.GopayException;
import com.gopay.common.order.dao.CommonQueryDAO;
import com.gopay.common.util.DateUtils;
import com.gopay.common.util.HttpTransport;
import com.gopay.common.util.NamedThreadFactory;
import com.gopay.common.util.StringTools;
import com.gopay.common.util.spring.SpringContextUtil;
import com.gopay.gass.service.GassPayRemoteService;
import com.gopay.sms.client.GopaySMSHttpClient;
import com.gopay.sms.common.constants.SMSAppId;
import com.gopay.sms.common.domain.RequestVO;
import com.gopay.sms.common.domain.ResponseVO;

/**
 * 工行银企发起多线程任务
 * 
 * @IcbcBcdlOrderSendMTJob.java
 * @author lilili
 * @data
 */
public class IcbcBcdlOrderSendMTJob {

	private Logger logger = LoggerFactory.getLogger(IcbcBcdlOrderSendMTJob.class);

	private  int maxThreadCount;

	private int maxSize;

	private String bank;

	private String across;

	private String label;
	
	private static final String SWITCH_ON = "on";

    @Resource(name = "smsConfig")
    private Map<String, String> smsConfig;

	@Resource(name = "bankOrderManager")
	protected BankOrderManager bankOrderManager;

	@Resource(name = "bisBankOrderQueryDAO")
	private BisBankOrderQueryDAO bisBankOrderQueryDAO;

	@Resource(name = "commonQueryDAO")
	private CommonQueryDAO commonQueryDAO;

	private BcdlOrderSentinel sentinel;

	@Resource(name = "bcdlConfig")
	private Map<String, String> bcdlConfig;
	
	@Resource(name = "gassPayService")
	private GassPayRemoteService gassPayService;

	private ThreadPoolExecutor threadPool;

	@PostConstruct
	public void threadPoolCreate() {
		threadPool = (ThreadPoolExecutor) Executors.newFixedThreadPool(maxThreadCount, new NamedThreadFactory("icbcBcdlOrderSendMTJob--"+across));
	}
	
	private void printLog(StopWatch sw, String bank, String prefix) {
		sw.split();
		logger.error(prefix + "------,bank=" + bank+  ",耗时="
				+ sw.getSplitTime());
	}
	
	
	public final void doJob() {

		StopWatch sw = new StopWatch();
		sw.start();
		printLog(sw, bank,"IcbcBcdlOrderSendMTJob--"+across+"------start");
		
		long startMillions = System.currentTimeMillis();
		if (sentinel.isFuse()) {
			logger.error("IcbcBcdlOrderSendMTJob--"+across+"---"+bank + "多线程付款熔断" );
			
			oneTestOrder(bank, sentinel);
			return;
		}
		
		//日切判断
		if(!bankOrderManager.isLegalTime(bank)){
			return;
		}
		
		if (!isLargeCrossWorkTime(new Date())) {
			// 工行跨行大额订单增加发送时间限制
			if (SettlementExchangeConstants.STLM_EX_ICBC_CROSS.equals(getStlmExByAcross(across))) {
				logger.error(" IcbcBcdlOrderSendMTJob--"+across+" process not isLargeCrossWorkTime across,"+across);
				return;
			}
		}
		
		List<String> bankOrders = getSendBankOrder(bank);
				
		
		printLog(sw, bank,"IcbcBcdlOrderSendMTJob--"+across+"------list.size()=" + bankOrders.size());
		
		callBis(bankOrders);
		
		printLog(sw, bank,"IcbcBcdlOrderSendMTJob--"+across+"------end");
		sw.stop();

		long endMiilions = System.currentTimeMillis();
		long result = (endMiilions - startMillions) / 1000;
		logger.info("IcbcBcdlOrderSendMTJob--"+across+" 异步多线程处理结束 耗费=" + result + "秒");

	}

	public void callBis(List<String> bankOrders) {

		// 订单队列
		if (!(bankOrders.isEmpty())) {
			CountDownLatch countDownLatch = new CountDownLatch(bankOrders.size());

			logger.info("多线程开始调用...");
			// 线程池中线程全部参与
			for (String gopayOrderId : bankOrders) {
				BankOrder order = bisBankOrderQueryDAO.find(BankOrder.class, gopayOrderId);
				// 非生产环境、非一分钱交易直接置为“99”失败
				if (!"prod".equals(bcdlConfig.get("sys.id")) && !"0.01".equals(order.getSendBankAmt().toString())) {
					bankOrderManager.updateFail(order);
				} else {
					if (!isLargeCrossWorkTime(new Date())) {
						// 工行跨行大额订单增加发送时间限制
						if (SettlementExchangeConstants.STLM_EX_ICBC_CROSS.equals(getStlmEx(order.getOrderId()))) {
							logger.error(order.getOrderId() + " IcbcBcdlOrderSendMTJob process not isLargeCrossWorkTime");
							continue;
						}
					}
					IcbcBcdlOrderSendMTTaskThread task = new IcbcBcdlOrderSendMTTaskThread(label, bank, order, countDownLatch, sentinel, bankOrderManager,gassPayService);
					threadPool.execute(task);
				}
			}
			try {
				countDownLatch.await(120, TimeUnit.SECONDS);
				if(countDownLatch.getCount()!=0){
					logger.error("IcbcBcdlOrderSendMTJob--"+across+"付款任务超时未处理完，做熔断处理...");
					sentinel.stop(2);
					sendSms("IcbcBcdlOrderSendMTJob--"+across+"付款多线程任务超时未处理完成。");
				}
				logger.info("IcbcBcdlOrderSendMTJob--"+across+"多线程调用完毕..");
			} catch (InterruptedException e) {
				logger.error("IcbcBcdlOrderSendMTJob--"+across+"countdown error", e);
			}
			monitorThread();
		} else {
			logger.info("IcbcBcdlOrderSendMTJob--"+across+" 取回列表为空...");
		}
	}
	
	private void sendSms(String msg) {

		try {
			if (SWITCH_ON.equals(smsConfig.get("switch"))) {
				// 自定义短信内容，不提供验证码校验功能
				RequestVO req = new RequestVO();
				req.setAppId(SMSAppId._90000.value);
				req.setMobile(smsConfig.get("mobile"));// 电话号码(多个以逗号分隔)
				req.setContent(DateUtils.getCurrDate() + "," + bank + " 熔断:" + msg);
				ResponseVO resp = GopaySMSHttpClient.getInstance().createVerificationCode(req);

				if (resp != null) {
					if ("100".equals(resp.getStatusCode())) {
						logger.error("[sentinel_sms]银行【" + bank + "】熔断短信发送成功");
					} else {
						logger.error("[sentinel_sms]银行【" + bank + "】熔断短信发送失败，原因：" + resp.getMessage());
					}
				}
			} else {
				logger.error("[sentinel_sms] 熔断短信发送已关闭，信息: 银行" + bank);

			}
		} catch (Exception e) {
			logger.error("[sentinel_sms]senSMS error: ", e);
		}

	}

	private void monitorThread() {
		String date = DateUtils.getCurrDate();
		int size = threadPool.getQueue().size();
		int threadSize = threadPool.getPoolSize();
		int activeThreadSize = threadPool.getActiveCount();
		long taskCount = threadPool.getTaskCount();
		long completedTaskCount = threadPool.getCompletedTaskCount();
		long largestPoolSize = threadPool.getLargestPoolSize();

		StringBuffer sb = new StringBuffer("线程池参数监控=");
		sb.append("date=" + date);
		sb.append("size=" + size);
		sb.append(",threadSize=" + threadSize);
		sb.append(",activeThreadSize=" + activeThreadSize);
		sb.append(",taskCount=" + taskCount);
		sb.append(",completedTaskCount=" + completedTaskCount);
		sb.append(",largestPoolSize=" + largestPoolSize);

		logger.info(sb.toString());
	}

	public List<String> getSendBankOrder(String bank) {
		List<Object[]> list = getCheckBcdlOrderByBank(bank, BankOrderStatus.ACCEPTED, across, 1, maxSize);
		List<String> orderIds = new LinkedList<String>();

		for (Object[] bankOrderId : list) {
			logger.error("orderId:" + bankOrderId[0]);
			orderIds.add(StringTools.checkNulltoString(bankOrderId[0]));
		}
		return orderIds;
	}

	public List<Object[]> getCheckBcdlOrderByBank(String bank, String orderStatus, String across, int pageIndex, int pageSize) {
		StringBuffer sql = new StringBuffer(250);
		sql.append("select b.GOPAY_OUT_ORDER_ID,b.GOPAY_ORDER_ID from BIS_BANK_MAIN_ORDER b ");
		if (StringUtils.isNotBlank(across) || "ICBC".equals(bank)) {
			sql.append(" left join BIS_BANK_BCDL_ORDER o on o.gopay_out_order_id = b.gopay_out_order_id ");
		}
		sql.append("left join checkorder_bcdl_send_bank_log c on b.gopay_order_id = c.gopay_order_id ");
		sql.append(" where b.ORDER_STATUS = :orderStatus ");
		sql.append(" and b.BANK_BIZ_PROD_CODE = :bankBizProdCode ");
        // 获取订单增加渠道码条件
        sql.append(" and b.INTRFACE_CHANNEL_CODE = :interfaceChennelCode ");
		sql.append(" and b.ORDER_TYPE = :orderType ");
		sql.append(" and b.GOPAY_BANK_CODE = :bankCode ");
		if (StringUtils.isNotBlank(across)) {
		    sql.append(" and o.IS_CROSSBANK = :across ");
		}
		sql.append(" and b.TOTAL_CHECK_TIMES <= :totalCheckTimes ");
		sql.append(" and b.SEND_BANK_TM >=to_date(:beginTime ,'yyyyMMddHH24miss') ");
		sql.append(" and c.gopay_order_id is null");
		// 修改T+1插队到T+0问题，改成订单号降序
		//sql.append(" order by b.TOTAL_CHECK_TIMES, b.GOPAY_ORDER_ID desc ");
		sql.append(" order by  b.SEND_BANK_TM asc ");

		Map<String, Object> params = new HashMap<String, Object>();
		params.put("orderStatus", orderStatus);
		params.put("bankBizProdCode", BisBankBusinessProd._09.value);
        params.put("interfaceChennelCode", BisBankInterfaceChannel._00.value);
		params.put("orderType", BankOrderType.GOPAY_BIS_ORDER);
		params.put("bankCode", bank);
		if(StringUtils.isNotBlank(across)){
            params.put("across", across);
        }
		params.put("totalCheckTimes", 7);
		params.put("beginTime", recentlyTime());
		List<Object[]> list = commonQueryDAO.findBySql(sql.toString(), params, pageIndex, pageSize);
		return list;
	}

	private String recentlyTime() {
		Calendar calendar = Calendar.getInstance();

		// 开始时间--最近1天
		calendar.setTime(new Date());
		calendar.add(Calendar.DAY_OF_MONTH, -2);
		String beginTime = DateUtils.format(calendar.getTime(), "yyyyMMddHHmmss");
		return beginTime;
	}

	private String getStlmEx(String gopayOrderId) {
		StringBuffer sql = new StringBuffer(250);
		Map<String, Object> map = new HashMap<String, Object>();
		sql.append(" SELECT ").append(" STLM_EX, GOPAY_ORDER_ID ").append(" FROM CPS_GEN_PAY_ORDER cp ").append(" WHERE 1=1 ").append(" AND cp.GOPAY_ORDER_ID =:gopayOrderId");
		map.put("gopayOrderId", gopayOrderId);
		List<Object[]> list = commonQueryDAO.findBySql(sql.toString(), map);
		if (list != null && list.size() > 0) {
			return StringTools.checkNulltoString(list.get(0)[0]);
		}
		return null;
	}
	
	private String getStlmExByAcross(String across) {
		if(across.equals("0")){
			return SettlementExchangeConstants.STLM_EX_ICBC;
		}else if(across.equals("1")){
			return SettlementExchangeConstants.STLM_EX_ICBC_SUPER;
		}else if(across.equals("2")){
			return SettlementExchangeConstants.STLM_EX_ICBC_CROSS;
		}else{
			return null;
		}
	}

	private boolean isLargeCrossWorkTime(Date date) {
		int hour = date.getHours();
		int minute = date.getMinutes();
		if (hour < 9 || hour > 16 || (hour == 16 && minute > 50) || (hour == 9 && minute < 10)) {
			logger.info("IcbcBcdlOrderSendMTJob isLargeCrossWorkTime false");
			return false;
		}
		return true;
	}
	
	private void oneTestOrder(String bank, BcdlOrderSentinel sentinel){
		logger.error("bcdl_" + bank + ",执行一分钱测试操作，start");
		//获取该熔断器下最新的一笔11状态的一分钱测试订单
		try {
			BankOrder order = bankOrderManager.getSendOneTestOrder(sentinel.getSentinelId());
			if(order == null){
				return ;
			}
			String beanName = getSendBankBeanName(bank);

			if (false == SpringContextUtil.containsBean(beanName)) {
				logger.info("spring bean does not exist: " + beanName);
				return;
			}

			BcdlOrderSendBankService sendBankService = (BcdlOrderSendBankService) SpringContextUtil.getBean(beanName);

			HttpTransport notifyGass = new HttpTransport();
			notifyGass.setSendEncoding("UTF-8");


			logger.error("一分钱测试多线程任务银企直连 发送银行--开始 ，orderId=" + order.getOrderId() + ",bank=" + bank);
			BcdlResult result = sendBankService.sendBank(order, label);
			logger.error("一分钱测试多线程任务银企直连 发送银行--结束 ，orderId=" + order.getOrderId() + ",bank=" + bank + ",resultCode=" + result.getResultCode());

			Map<String, String> param = new HashMap<String, String>();
			if (ProcCodeConstants.PROC_CODE_100E6027.equals(result.getResultCode())) {
				param.put("resultCode", "2");
				param.put("resultMsg", result.getErrorMsg());
				param.put("remark", "1");
				param.put("gopayOrderId", order.getOrderId());
				//add by zyt,2017-10-13 ,是否需要做失败的拒绝账务，
				if(result.isNeedRefund()){
					logger.info("发起失败拒绝账务：orderId:" + order.getOrderId());
					param.put("needRefund", "1");
				}
				/*notifyGass.setUrl(bankOrder.getRemark2());
				notifyGass.submit(param);*/ //生产优化10期 lx httpclient 替换为 hessian
                gassPayService.bcdlNotifyGass(param);
			} else if (ProcCodeConstants.PROC_CODE_100S1000.equals(result.getResultCode())) {
				param.put("resultCode", "1");
				param.put("remark", "1");
				param.put("gopayOrderId", order.getOrderId());
				/*notifyGass.setUrl(bankOrder.getRemark2());
				notifyGass.submit(param);*/ //生产优化10期 lx httpclient 替换为 hessian
                gassPayService.bcdlNotifyGass(param);
			} else if (ProcCodeConstants.PROC_CODE_100E6047.equals(result.getResultCode())) {
				logger.error("多线程任务银企直连 超时休眠--开始 ，orderId=" + order.getOrderId() + ",bank=" + bank);
				Thread.sleep(3000);
				logger.error("多线程任务银企直连 超时休眠--结束 ，orderId=" + order.getOrderId() + ",bank=" + bank);
			} else {
				param.put("resultCode", "3");
				param.put("remark", "1");
				param.put("gopayOrderId", order.getOrderId());
				/*notifyGass.setUrl(bankOrder.getRemark2());
				notifyGass.submit(param);*/ //生产优化10期 lx httpclient 替换为 hessian
                gassPayService.bcdlNotifyGass(param);
			}
		} catch (GopayException | InterruptedException e) {
			logger.error("bcdl_" + bank + ",一分钱测试出现异常");
			e.printStackTrace();
		}
		//根据返回的
		logger.error("bcdl_" + bank + ",执行一分钱测试操作，end");
	}
	private String getSendBankBeanName(String bankCode) throws GopayException {
		String beanName = bankCode + "-BCDL";
		return beanName;
	}

	public void setMaxThreadCount(int maxThreadCount) {
		this.maxThreadCount = maxThreadCount;
	}
	public void setMaxSize(int maxSize) {
		this.maxSize = maxSize;
	}
	public void setBank(String bank) {
		this.bank = bank;
	}

	public void setAcross(String across) {
		this.across = across;
	}

	public void setLabel(String label) {
		this.label = label;
	}

	public void setSentinel(BcdlOrderSentinel sentinel) {
		this.sentinel = sentinel;
	}

	public BankOrderManager getBankOrderManager() {
		return bankOrderManager;
	}

	public void setBankOrderManager(BankOrderManager bankOrderManager) {
		this.bankOrderManager = bankOrderManager;
	}

	public BisBankOrderQueryDAO getBisBankOrderQueryDAO() {
		return bisBankOrderQueryDAO;
	}

	public void setBisBankOrderQueryDAO(BisBankOrderQueryDAO bisBankOrderQueryDAO) {
		this.bisBankOrderQueryDAO = bisBankOrderQueryDAO;
	}

	public CommonQueryDAO getCommonQueryDAO() {
		return commonQueryDAO;
	}

	public void setCommonQueryDAO(CommonQueryDAO commonQueryDAO) {
		this.commonQueryDAO = commonQueryDAO;
	}

}
